package controller

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"

	"gitee.com/liuyun-luo/MagicStreamMovies/Server/config"
	"gitee.com/liuyun-luo/MagicStreamMovies/Server/global"
	"gitee.com/liuyun-luo/MagicStreamMovies/Server/models"
	"gitee.com/liuyun-luo/MagicStreamMovies/Server/utils"
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"github.com/go-redis/redis"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// 参数验证器
var vali = validator.New()

type CursorInputDTO struct {
	//起始游标（本质是电影的ID，首次请求时可为0）
	Cursor uint `json:"cursor"`
	//请求的电影的数量
	PageSize uint `binding:"required" json:"page_size"`
	//电影的类别
	Category string `json:"category"`
	//排序方式
	SortBy string `json:"sort_by" binding:"required,oneof=rating like_count play_count collect_count created_at"`
	//排序方向
	SortDirection string `json:"sort_direction" binding:"required,oneof=desc asc"`
	//发起请求的用户的id
	UserId uint `json:"user_id"`
}

// 游标分页查询电影
func GetMoviesByCursor(ctx *gin.Context) {
	var InputDTO CursorInputDTO

	//校验并取出参数
	if err := ctx.ShouldBindJSON(&InputDTO); err != nil {
		ctx.JSON(400, gin.H{
			"error": err.Error(),
		})
		return
	}
	var movies []models.Movie
	var nextCursor uint
	var err error

	//游标分页查询
	movies, nextCursor, err = GetMoviesWithPaginationCursor(InputDTO)
	if err != nil {
		ctx.JSON(500, gin.H{
			"error": err.Error(),
		})
		return
	}

	ctx.JSON(200, gin.H{
		"movies":      movies,
		"next_cursor": nextCursor,
	})
}

// 游标分页实现
func GetMoviesWithPaginationCursor(input CursorInputDTO) ([]models.Movie, uint, error) {
	var movies []models.Movie
	var nextCursor uint

	db := global.MySQLDb

	// 构建基础查询
	query := db.Model(&models.Movie{})

	// 1. 按电影类别筛选
	if input.Category != "" {
		query = query.Joins("JOIN movie_genres ON movies.id = movie_genres.movie_id").
			Joins("JOIN genres ON movie_genres.genre_id = genres.id").
			Where("genres.genre_name = ?", input.Category).
			Group("movies.id")
	} else if input.UserId != 0 {
		// 如果没有指定类别，则使用用户喜欢的电影类型进行筛选
		var user models.User

		if err := db.Where("id = ?", input.UserId).First(&user).Error; err != nil {
			return nil, 0, fmt.Errorf("没有找到id为%v的用户: %w", input.UserId, err)
		}

		// 如果用户没有喜欢的类型，返回空结果
		if len(user.FavouriteGenres) == 0 {
			return []models.Movie{}, 0, nil
		}

		// 提取用户喜欢的类型ID
		var genreIDs []uint
		for _, genre := range user.FavouriteGenres {
			genreIDs = append(genreIDs, genre.ID)
		}

		// 使用JOIN查询用户喜欢类型的电影
		query = query.Joins("JOIN movie_genres ON movies.id = movie_genres.movie_id").
			Where("movie_genres.genre_id IN (?)", genreIDs).
			Group("movies.id") // 避免重复记录
	} else { //如果用户id也为空，就不分类了，直接排序
	}

	// 2. 处理游标分页
	if input.Cursor > 0 {
		// 获取当前游标位置的电影记录，用于后续比较
		var currentMovie models.Movie
		if err := db.First(&currentMovie, input.Cursor).Error; err != nil {
			return nil, 0, err
		}

		// 解析排序字段和排序方向
		var sortField, sortDirection string = input.SortBy, input.SortDirection

		if sortDirection != "asc" { //如果不是升序
			sortDirection = "desc"
		}

		// 根据排序字段和方向构建游标条件
		switch sortField {
		case "rating": //按评分排序
			if sortDirection == "desc" {
				query = query.Where("rating < ? OR (rating = ? AND movies.id >= ?)",
					currentMovie.Rating, currentMovie.Rating, input.Cursor)
			} else {
				query = query.Where("rating > ? OR (rating = ? AND movies.id >= ?)",
					currentMovie.Rating, currentMovie.Rating, input.Cursor)
			}
		case "like_count": //按点赞排序
			if sortDirection == "desc" {
				query = query.Where("like_count < ? OR (like_count = ? AND movies.id >= ?)",
					currentMovie.LikeCount, currentMovie.LikeCount, input.Cursor)
			} else {
				query = query.Where("like_count > ? OR (like_count = ? AND movies.id >= ?)",
					currentMovie.LikeCount, currentMovie.LikeCount, input.Cursor)
			}
		case "play_count": //按播放量排序
			if sortDirection == "desc" {
				query = query.Where("play_count < ? OR (play_count = ? AND movies.id >= ?)",
					currentMovie.PlayCount, currentMovie.PlayCount, input.Cursor)
			} else {
				query = query.Where("play_count > ? OR (play_count = ? AND movies.id >= ?)",
					currentMovie.PlayCount, currentMovie.PlayCount, input.Cursor)
			}
		case "collect_count": //按收藏排序
			if sortDirection == "desc" {
				query = query.Where("collect_count < ? OR (collect_count = ? AND movies.id >= ?)",
					currentMovie.CollectCount, currentMovie.CollectCount, input.Cursor)
			} else {
				query = query.Where("collect_count > ? OR (collect_count = ? AND movies.id >= ?)",
					currentMovie.CollectCount, currentMovie.CollectCount, input.Cursor)
			}
		case "created_at": //按电影创建的时间排序
			if sortDirection == "desc" {
				query = query.Where("created_at < ? OR (created_at = ? AND movies.id >= ?)",
					currentMovie.CreatedAt, currentMovie.CreatedAt, input.Cursor)
			} else {
				query = query.Where("created_at > ? OR (created_at = ? AND movies.id >= ?)",
					currentMovie.CreatedAt, currentMovie.CreatedAt, input.Cursor)
			}
		default:
			// ID大于游标即可
			query = query.Where("movies.id >= ?", input.Cursor)
		}
	} else if input.Cursor == 0 { //首次分页请求为游标为0
		// ID大于游标即可
		query = query.Where("movies.id >= ?", input.Cursor)
	}

	// 3. 应用排序
	query = query.Order(input.SortBy + " " + input.SortDirection)

	// 4. 查询比请求数量多一条记录，用于判断是否还有下一页
	query = query.Limit(int(input.PageSize) + 1)

	// 执行查询
	if err := query.Preload("GenreList").Find(&movies).Error; err != nil {
		return nil, 0, err
	}

	// 5. 处理结果，设置下一个游标
	if len(movies) > int(input.PageSize) {
		// 有下一页，返回第input.PageSize条记录的ID作为下一个游标
		nextCursor = movies[input.PageSize].ID
		movies = movies[:input.PageSize] // 截断结果
	} else {
		nextCursor = 0 // 没有下一页
	}

	return movies, nextCursor, nil
}

// 新增电影
func AddMovie(ctx *gin.Context) {
	var movie models.Movie
	//读取参数
	if err := ctx.ShouldBindJSON(&movie); err != nil {
		ctx.JSON(400, gin.H{
			"error": "参数解析失败，err: " + err.Error(),
		})
		return
	}

	//插入电影数据库
	if err := global.MySQLDb.Create(&movie).Error; err != nil {
		ctx.JSON(500, gin.H{
			"error": "插入数据库失败！err: " + err.Error(),
		})
		return
	}

	//返回创建的电影的信息
	ctx.JSON(200, &movie)
}

// 新增多部电影
func AddMovieList(ctx *gin.Context) {
	var movie_list []models.Movie

	//读取参数
	if err := ctx.ShouldBindJSON(&movie_list); err != nil {
		ctx.JSON(400, gin.H{
			"error": "参数解析失败，err: " + err.Error(),
		})
		return
	}

	//插入电影数据库
	if err := global.MySQLDb.Create(&movie_list).Error; err != nil {
		ctx.JSON(500, gin.H{
			"error": "插入数据库失败！err: " + err.Error(),
		})
		return
	}

	//返回创建的电影的信息
	ctx.JSON(200, gin.H{
		"msg": "插入多部电影成功！",
	})
}

// 获取指定电影
func GetMovie(ctx *gin.Context) {
	movie_id := ctx.Param("movie_id")
	if movie_id == "" {
		ctx.JSON(400, gin.H{
			"error": "电影id不能为空",
		})
		return
	}

	Zset := utils.NewMoviesPopularityZSet(global.RedisDB, config.MoviesPopularityZSetKeyName)
	mov, err := global.RedisDB.Get(config.MovieKeyName + ":" + movie_id).Result()
	if err == redis.Nil { //缓存未命中
		fmt.Println("电影id: " + movie_id + "缓存未命中")

		var movie models.Movie

		//寻找一个电影
		if err = global.MySQLDb.Preload("GenreList").Where("id = ?", movie_id).First(&movie).Error; err != nil {
			ctx.JSON(404, gin.H{
				"error": err.Error(),
			})
			return
		}

		//序列化为json
		movie_json, er := json.Marshal(movie)
		if er != nil {
			ctx.JSON(500, gin.H{
				"error": er.Error(),
			})
			return
		}
		// 计算电影热度，并把电影添加到ZSet，添加成功就返回电影当前排名和分数
		ranking, fe, err := Zset.Add(&movie)
		if err != nil {
			log.Println(err.Error())
		}

		fmt.Printf("movie_id: %d,排名: %d,分数: %f\n", movie.ID, ranking, fe)

		//如果排名小于100,就认为是热点电影
		if ranking <= 100 {
			//把电影载入缓存，并设置较长的超时时间
			if err := global.RedisDB.Set(config.MovieKeyName+":"+movie_id, movie_json, config.GetHighFrequentCacheExpirationTime()).Err(); err != nil {
				log.Printf("把热点电影添加到缓存失败，movie_id: %d,err: %s", movie.ID, err.Error())
			}
		} else {
			//把电影载入缓存，并设置普通的超时时间
			if err := global.RedisDB.Set(config.MovieKeyName+":"+movie_id, movie_json, config.GetDefaultCacheExpirationTime()).Err(); err != nil {
				log.Printf("把普通电影添加到缓存失败，movie_id: %d,err: %s", movie.ID, err.Error())
			}
		}

		//返回结果
		ctx.JSON(200, &movie)

	} else if err == nil { //缓存命中
		fmt.Println("电影id: " + movie_id + "缓存命中")

		var movie models.Movie

		//反序列化回来
		if err := json.Unmarshal([]byte(mov), &movie); err != nil {
			ctx.JSON(500, gin.H{
				"error": err.Error(),
			})
			return
		}

		//更新分数
		_, _, err := Zset.Add(&movie)
		if err != nil {
			log.Println(err.Error())
		}

		ctx.JSON(200, &movie)
	} else {
		ctx.JSON(500, gin.H{
			"error": err.Error(),
		})
		return
	}
}

// 点赞电影
func LikeMovie(ctx *gin.Context) {
	//获取要点赞的电影的id
	movie_id := ctx.Param("movie_id")
	if movie_id == "" {
		ctx.JSON(400, gin.H{
			"error": "参数错误,电影id不能为空",
		})
		return
	}

	//拼接key
	like_key_name := config.MovieKeyName + ":" + movie_id + ":" + config.MovieLikeCountKeyName

	//尝试从Redis中读取电影当前的点赞
	if err := global.RedisDB.Get(like_key_name).Err(); err == redis.Nil { //缓存未命中
		fmt.Printf("缓存未命中,movie_id: %s\n", movie_id)

		var like uint64
		//从数据库中读取
		if err := global.MySQLDb.Model(&models.Movie{}).Where("id = ?", movie_id).Pluck("like_count", &like).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				ctx.JSON(404, gin.H{
					"error": "电影不存在，err: " + err.Error(),
				})
			} else {
				ctx.JSON(500, gin.H{
					"error": "数据库操作失败" + err.Error(),
				})
			}
			return
		}

		//点赞数+1
		like++

		//启动Redis的事务
		pipe := global.RedisDB.TxPipeline()
		{
			//写入到Redis
			pipe.Set(like_key_name, like, config.GetDefaultCacheExpirationTime())
			//把被点赞的电影的id添加到set中,如果set不存在会自动创建
			pipe.SAdd(config.MoviesLikeDirtySetKeyName, movie_id)
		}
		//执行事务
		results, err := pipe.Exec()
		if err != nil {
			ctx.JSON(500, gin.H{
				"msg":   "点赞失败！",
				"error": err.Error(),
			})
			return
		}

		setResult, ok := results[0].(*redis.StatusCmd)
		if ok {
			log.Printf("SET 命令：%v\n", setResult.Val())
		}

		//返回结果
		ctx.JSON(200, gin.H{
			"msg":        "点赞成功!",
			"like_count": like,
		})
	} else if err == nil { //缓存命中
		fmt.Printf("缓存命中,movie_id: %s\n", movie_id)

		//启动Redis的事务
		pipe := global.RedisDB.TxPipeline()
		{
			//在redis中让点赞增加1
			pipe.Incr(like_key_name)

			//把被点赞的电影的id添加到set中,如果set不存在会自动创建
			pipe.SAdd(config.MoviesLikeDirtySetKeyName, movie_id)
		}
		//执行点赞操作
		results, err := pipe.Exec()
		if err != nil {
			ctx.JSON(500, gin.H{
				"msg":   "点赞失败！",
				"error": err.Error(),
			})
			return
		}

		incrResult, ok := results[0].(*redis.IntCmd)
		if ok {
			log.Printf("Incr 命令：%v\n", incrResult.Val())
			//返回结果
			ctx.JSON(200, gin.H{
				"msg":         "点赞成功!",
				"liken_count": incrResult.Val(),
			})
		}

	} else { //操作失败
		ctx.JSON(500, gin.H{
			"error": "Redis操作失败" + err.Error(),
		})
		return
	}
}

// 删除一部电影--只有管理员拥有该操作的执行权限
func DeleteMovice(ctx *gin.Context) {
	movie_id := ctx.Param("movie_id")
	if movie_id == "" {
		ctx.JSON(400, gin.H{
			"error": "参数错误,电影id不能为空",
		})
		return
	}

	//先查询，判断电影是否存在
	var movie models.Movie
	if err := global.MySQLDb.First(&movie, movie_id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) { //如果没找到
			ctx.JSON(404, gin.H{
				"error": "数据库中找不到该电影,movie_id" + movie_id,
			})
		} else {
			ctx.JSON(500, gin.H{
				"error": "数据库操作失败，err: " + err.Error(),
			})
		}
		return
	}

	//启动事务--必须启动事务，加上Select(clause.Associations)会生成  多条SQL
	if err := global.MySQLDb.Transaction(func(tx *gorm.DB) error {
		//删除该电影的所有评论--可以去掉，Select(clause.Associations)能够完成
		/*if err := tx.Where("movie_id = ?", movie_id).Delete(&models.MovieReview{}).Error; err != nil {
			return errors.New("删除电影的评论失败,err: " + err.Error())
		}*/

		//删除电影，加上Select(clause.Associations)之后，所有的关联关系gorm会自动删除
		if err := tx.Select(clause.Associations).Unscoped().Delete(&movie).Error; err != nil {
			return errors.New("删除电影以及电影的关联关系失败,err: " + err.Error())
		}
		return nil
	}); err != nil {
		ctx.JSON(500, gin.H{
			"error": "删除操作失败，err: " + err.Error(),
		})
		return
	}

	//删除电影的缓存
	key_name := config.MovieKeyName + ":" + movie_id
	if err := global.RedisDB.Del(key_name).Err(); err == redis.Nil || err == nil { //键不存在或删除成功
		ctx.JSON(200, gin.H{
			"error": "删除操作成功！",
		})
	} else {
		log.Printf("Redis检查键: %s 是否存在时，发生错误,err: %s", key_name, err.Error())
		ctx.JSON(200, gin.H{
			"error": "删除操作成功！",
		})
	}
}
